#ifndef SIMPLE_SUPPORT_FUNCTION_UTILS_HPP
#define SIMPLE_SUPPORT_FUNCTION_UTILS_HPP
#include <type_traits>
#include <tuple>
#include <functional>

namespace simple { namespace support
{

	template <typename Function>
	struct fun_signature;

	template <typename ReturnType, typename... ArgumentType>
	struct fun_signature<ReturnType(ArgumentType...)>
	{
		using return_type = ReturnType;
		using argument_type = std::tuple<ArgumentType...>;
	};

	template <typename Function, int param_index = 0>
	struct fun_param
	{
		using type = std::tuple_element_t<param_index, typename fun_signature<Function>::argument_type>;
	};

	template <typename Function, int param_index = 0>
	using fun_param_t = typename fun_param<Function, param_index>::type;

	template <typename Function>
	using fun_return_t = typename fun_signature<Function>::return_type;

	template <typename ReturnType, typename... ArgumentType>
	inline constexpr ReturnType nop(ArgumentType...) {}

	// not short for "to overload" but for "overload set"
	template<typename... Fs> struct overload : Fs...  { using Fs::operator()...; };
	template<typename... Fs> overload(Fs...) -> overload<Fs...>;

	// TODO hifunc
	// TODO 1_arg + 2

	// std::invoke is not constexpr, until c++20 -_-
	template< class F, class... Args>
	constexpr std::invoke_result_t<F, Args...>
	invoke(F&& f, Args&&... args)
	noexcept(std::is_nothrow_invocable_v<F,Args...>)
	{
		return std::apply(std::forward<F>(f),
			std::forward_as_tuple(std::forward<Args>(args)...));
	}

	// TODO: if transform is tuple of functions apply them each to matching arguments // already have use case for this, i nested instead and it was super confusing ToT
	// TODO: if transform is tuple + reverse tag apply them each to arguments matching in reverse order
	// TODO: if transform is tuple with index sequence apply them to each arguments matching in specified sequence
	// TODO: missing elements in tuple are considered identity
	// TODO: make convenience types like transform_second_arg, or transform arg that accepts index sequence, and or single index
	template<typename Transform, typename Function = std::less<>>
	struct transform_arg {
		Transform transform;
		Function function = {};
		template <typename... Args>
		constexpr decltype(auto) operator()(Args&&... args)
			// TODO
			// noexcept(noexcept(
			// 	auto
			// ))
			// conditional const
		{
			return (support::invoke(function,
				// transform if invocable otherwise just forward
				support::invoke(transform, std::forward<Args>(args))...));
		}

		// TODO: when function objects are references need operator= to make them kind of regularish
	};
	template<typename Transform, typename Function>
	transform_arg(Transform&&, Function&&) -> transform_arg<Transform, Function>;
	template<typename Transform>
	transform_arg(Transform&&) -> transform_arg<Transform, std::less<>>;

	// TODO this is similar to transform_arg, except there functions are applied to corresponding arg while here they are applied to all args
	// looking at arguments as a collection, the general transform_arg is a transform accumulate, while general this is accumulate accumulate??
	template <typename... Predicate>
	struct disjunction_f
	{
		std::tuple<Predicate...> predicates;

		template <typename... P>
		disjunction_f(P&&... predicates) :
			predicates(std::forward<P>(predicates)...)
		{}

		template <typename... Args>
		constexpr auto operator()(Args&&... args) // conditional const, noexcept
		{
			return std::apply([args = std::forward_as_tuple(std::forward<Args>(args)...)]
				(auto&&... predicates)
				{
					// TODO: to generalize the op need to pack expand into nested function calls
					// could that affect short circuit? shouldn't if inlined, right??
					return (std::apply(predicates, args) || ...);
				},
				predicates
			);
		}
	};
	template <typename... Predicate>
	disjunction_f(Predicate&&...) -> disjunction_f<Predicate...>;
	template <typename P1, typename P2>
	disjunction_f(P1&&,P2&&) -> disjunction_f<P1,P2>;

}} // namespace simple::support

#endif /* end of include guard */
